﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

namespace LocalOverride.Fiddler.UrlMapping
{
    public class DirectoryMapping : IMapping
    {
        private readonly Uri url;
        private readonly string directoryPath;
        private readonly bool downloadMissingFile;
        private readonly ISpecialCaseMappingsProvider specialCaseMappingsProvider;
        private readonly string defaultFileName;
        private readonly Regex extensionsMatchRegex;
        private readonly Regex uniqueQueryStringExtensionsRegex;
        public DirectoryMapping(Uri url, string directoryPath)
            : this(url, directoryPath, new List<string>(), false, "", new List<string>(), null)
        {

        }

        public DirectoryMapping(Uri url, string directoryPath, IEnumerable<string> extensions, bool downloadMissingFile, string defaultFileName, IEnumerable<string> uniqueQueryStringExtensions, ISpecialCaseMappingsProvider specialCaseMappingsProvider)
        {
            this.url = url;
           
            extensionsMatchRegex = GetExtensionsRegex(extensions);
            uniqueQueryStringExtensionsRegex = GetExtensionsRegex(uniqueQueryStringExtensions);

            this.directoryPath = directoryPath;
            this.downloadMissingFile = downloadMissingFile;
            this.specialCaseMappingsProvider = specialCaseMappingsProvider;
            this.defaultFileName = defaultFileName ?? "";
        }

        private Regex GetExtensionsRegex(IEnumerable<string> extensions)
        {
            if (extensions.Count() == 0)
                return null;

            var extensionsPattern = String.Join("|", extensions.ToArray());

            //from: http://stackoverflow.com/questions/169625/regex-to-check-if-valid-url-that-ends-in-jpg-png-or-gif
            var pattern = @"(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*\.(?:" +
                          extensionsPattern +
                          @"))(?:\?([^#]*))?(?:#(.*))?";
            return new Regex(pattern, RegexOptions.IgnoreCase);
        }

        public bool Matches(Uri request)
        {
            var trimmedRequest = RemoveQueryString(request);

            if (HostsMatch(url, trimmedRequest))
            {
                if (PathsMatch(url, trimmedRequest))
                {
                    if (ExtensionMatches(trimmedRequest))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private Uri RemoveQueryString(Uri requestUri)
        {
            var uriWithoutQueryString = new Uri(requestUri.GetLeftPart(UriPartial.Path));
            return uriWithoutQueryString;
        }

        private bool ExtensionMatches(Uri request)
        {
            if (extensionsMatchRegex == null)
                return true;

            return extensionsMatchRegex.IsMatch(request.ToString());
        }

        public MappingResult Map(Uri request)
        {
            
            if (specialCaseMappingsProvider != null)
            {
                var specialCaseResult = specialCaseMappingsProvider.GetMappingResult(request.ToString());

                specialCaseResult.DownloadMissingFile = downloadMissingFile;

                if (specialCaseResult != MappingResult.NoMapping)
                    return specialCaseResult;
            }

            var trimmedRequest = RemoveQueryString(request);

            string relativeLocation = GetRelativeLocation(url, trimmedRequest);
            string localPath = Path.Combine(directoryPath, relativeLocation);

            if (IsDirectoryPath(trimmedRequest))
            {
                localPath = Path.Combine(localPath, defaultFileName);
            }

            if (uniqueQueryStringExtensionsRegex != null)
                if (uniqueQueryStringExtensionsRegex.IsMatch(trimmedRequest.ToString()))
                {
                    if (specialCaseMappingsProvider != null)
                        localPath = specialCaseMappingsProvider.MakeSpecialCase(request.ToString(), localPath);
                }

            return new MappingResult(localPath, downloadMissingFile);
        }

        private bool IsDirectoryPath(Uri uri)
        {
            var absolutePath = uri.AbsolutePath;
            var lastSlash = absolutePath.LastIndexOf("/");

            if (lastSlash == -1)
                return false;

            var afterLastSlash = uri.AbsolutePath.Substring(lastSlash);

            return !afterLastSlash.Contains(".");
        }


        private bool HostsMatch(Uri x, Uri hostName)
        {
            return String.Equals(x.Host, hostName.Host, StringComparison.InvariantCultureIgnoreCase);
        }

        private bool PathsMatch(Uri mappingUri, Uri requestUri)
        {
            return (requestUri.PathAndQuery.StartsWith(mappingUri.PathAndQuery));
        }

        private string GetRelativeLocation(Uri mappingUri, Uri requestUri)
        {
            var relativeUrl = requestUri.ToString().Replace(mappingUri.OriginalString, "");

            if (relativeUrl.StartsWith("/")) relativeUrl = relativeUrl.Substring(1);

            var relativeDirectory = relativeUrl.Replace('/', '\\');
            return relativeDirectory;
        }
    }
}